/*
 * @Author       : Xu Xiaokang
 * @Email        :
 * @Date         : 2025-04-23 22:50:57
 * @LastEditors  : Xu Xiaokang
 * @LastEditTime : 2025-04-24 10:48:33
 * @Filename     :
 * @Description  :
*/

/*
! 模块功能: 用Verilog代码实现SPRAM功能
* 思路:
* 1.
~ 注意:
~ 1.
% 其它
*/

`default_nettype none

module mySPRAM
#(
  parameter RAM_STYLE = "block", //* RAM类型, 可选"block"(默认), "distributed"
  parameter DATA_WIDTH = 8, //* 数据位宽, 可选1, 2, 3, ..., 默认为8
  parameter ADDR_WIDTH = 6, //* RAM地址位宽, 对应RAM深度, 可选1, 2, 3, ..., 默认为6, 对应深度2**6=64
  parameter OPERATING_MODE_A = "WF", //* 可选"Write First"(默认), "Read First", "No Change"
  parameter USE_ENA = 0, //* 启用ENA信号
  parameter OUTPUT_REG_NUM_A = 1, //* A端口输出寄存器数量, 可选0(默认), 1, 2
  parameter INIT_FILE = "", //* 初始化文件名，空(默认)表示不初始化，示例目录C:/_myJGY/ram_init.coe
  /*
  * 默认初始值, 在未指定初始化文件或初始化文件行数比RAM深度小时起作用, 使用16进制表示, 默认值0,
  * 对应Vivado BRAM IP的功能Fill Remaining Memory Locations
  */
  parameter [DATA_WIDTH-1:0] INIT_VALUE_HEX = 'h0
)(
  input  wire                   clka,  //* A端口时钟
  input  wire                   ena,   //* A端口操作使能
  input  wire                   wea,   //* A端口写使能
  input  wire [DATA_WIDTH-1:0]  dina,  //* A端口输入数据
  input  wire [ADDR_WIDTH-1:0]  addra, //* A端口读/写地址
  output wire [DATA_WIDTH-1:0]  douta  //* A端口输出数据
);


//++ 参数有效性检查 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
initial begin
  if (RAM_STYLE != "block" && RAM_STYLE != "distributed")
    $error("RAM_STYLE must be \"block\" or \"distributed\"");
  if (DATA_WIDTH < 0)
    $error("DATA_WIDTH must be > 0");
  if (ADDR_WIDTH < 0)
    $error("ADDR_WIDTH must be > 0");
  if (OPERATING_MODE_A != "WF" && OPERATING_MODE_A != "RF" && OPERATING_MODE_A != "NC")
    $error("OPERATING_MODE_A must be \"WF\" , \"RF\" or \"NC\"");
  if (USE_ENA != 0 && USE_ENA != 1)
    $error("USE_ENA must be 0 or 1");
  if (OUTPUT_REG_NUM_A != 0 && OUTPUT_REG_NUM_A != 1 && OUTPUT_REG_NUM_A != 2)
    $error("OUTPUT_REG_NUM_A must be 0, 1, or 2");
end
//-- 参数有效性检查 ------------------------------------------------------------


// //++ 定义RAM空间 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
localparam DEPTH = 1 << ADDR_WIDTH; //* 等价于 2**ADDR_WIDTH
(* ram_style = RAM_STYLE *)
reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];
// //-- 定义RAM空间 ------------------------------------------------------------


//++ 初始化RAM ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
integer i;
generate
if (INIT_FILE != "") begin
  initial begin
    for (i = 0; i < DEPTH; i = i + 1) mem[i] = INIT_VALUE_HEX; //* 默认初始化值
    /*
    * 使用 $readmemb 从 .coe 文件加载初始值,
    * ug901中说明只有二进制格式的初始化文件才是可用Verilog代码进行初始化的
    */
    $readmemb(INIT_FILE, mem);
  end
end else begin
  initial begin : ram_init_all_0 //* 将每个存储单元初始化为设定的初始值
    for (i = 0; i < DEPTH; i = i + 1) mem[i] = INIT_VALUE_HEX;
  end
end
endgenerate
//-- 初始化RAM ------------------------------------------------------------


//++ 使能信号处理 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
wire true_ena = (USE_ENA == 0) ? 1'b1 : ena;
//-- 使能信号处理 ------------------------------------------------------------


//++ 端口写逻辑 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
always @(posedge clka) begin
  if (true_ena && wea)
    mem[addra] <= dina;
end
//-- 端口写逻辑 ------------------------------------------------------------


//++ 端口A读逻辑 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
reg [DATA_WIDTH-1:0] douta_tmp = 0;

generate
if (OPERATING_MODE_A == "WF") begin
  always @(posedge clka) begin
    if (true_ena)
      if (wea)
        douta_tmp <= dina;
      else
        douta_tmp <= mem[addra];
    else
      douta_tmp <= douta_tmp;
  end
end else if (OPERATING_MODE_A == "RF") begin
  always @(posedge clka) begin
    if (true_ena)
      douta_tmp <= mem[addra];
    else
      douta_tmp <= douta_tmp;
  end
end else begin
  always @(posedge clka) begin
    if (true_ena)
      if (~wea)
        douta_tmp <= mem[addra];
      else
        douta_tmp <= douta_tmp;
    else
      douta_tmp <= douta_tmp;
  end
end
endgenerate

generate
if (OUTPUT_REG_NUM_A == 0) begin
  assign douta = douta_tmp;
end else if (OUTPUT_REG_NUM_A == 1) begin
  reg [DATA_WIDTH-1:0] douta_tmp_r1 = 'd0;
  always @(posedge clka) begin
    if (true_ena)
      douta_tmp_r1 <= douta_tmp;
    else
      douta_tmp_r1 <= douta_tmp_r1;
  end
  assign douta = douta_tmp_r1;
end else begin
  reg [DATA_WIDTH-1:0] douta_tmp_r1 = 'd0;
  reg [DATA_WIDTH-1:0] douta_tmp_r2 = 'd0;
  always @(posedge clka) begin
    if (true_ena) begin
      douta_tmp_r1 <= douta_tmp;
      douta_tmp_r2 <= douta_tmp_r1;
    end else begin
      douta_tmp_r1 <= douta_tmp_r1;
      douta_tmp_r2 <= douta_tmp_r2;
    end
  end
  assign douta = douta_tmp_r2;
end
endgenerate
//-- 端口A读逻辑 ------------------------------------------------------------


endmodule
`resetall